Interfacing to C

When porting any large code from C to Cyclone, or even when writing a Cyclone program from scratch, you’ll want to be able to access legacy libraries. To do so, you must understand how Cyclone represents data structures, how it compiles certain features, and how to write wrappers to make up for representation mismatches.

Extern “C”

Sometimes, interfacing to C code is as simple as writing an appropriate interface. For instance, if you want to call the acos function which is defined in the C Math library, you can simply write the following:

  extern "C" double acos(double);

The extern “C” scope declares that the function is defined externally by C code. As such, it’s name is not prefixed with any namespace information by the compiler. Note that you can still embed the function within a Cyclone namespace, it’s just that the namespace is ignored by the time you get down to C code. If you have a whole group of functions then you can wrap them with a single extern “C” { … }, as in:

  extern "C" {
    double acos(double);
    float  acosf(float);
    double acosh(double);
    float  acoshf(float);
    double asin(double);
  }

You must be careful that the type you declare for the C function is its real type. Misdeclaring the type could result in a runtime error. Note that you can add Cyclonisms to the type that refine the meaning of the original C. For example, you could declare:

  extern "C" int strlen(const char * @notnull str);

Here we have refined the type of strlen to require that a non-NULL pointer is passed to it. Because this type is representation-compatible with the C type (that is, it has the same storage requirements and semantics), this is legal. However, the following would be incorrect:

  extern "C" int strlen(const char * @fat str);

Giving the function this type would probably lead to an error because Cyclone fat pointers are represented as three words, but the standard C library function expects a single pointer (one word).

The extern “C” approach works well enough that it covers many of the cases that you’ll encounter. However, the situation is not so when you run into more complicated interfaces. Sometimes you will need to write some wrapper code to convert from Cyclone’s representations to C’s and back (so called wrapper code).

Extern “C include”

Another useful tool is the extern “C include” mechanism. It allows you to write C definitions within a Cyclone file. Here is a simple example:

extern "C include" {
  char peek(unsigned int i) {
    return *((char *)i);
  }

  void poke(unsigned int i, char c) {
    *((char *)i) = c;
  }
} export {
  peek, poke;
}

In this example, we’ve defined two C functions peek and poke. Cyclone will not compile or type-check their code, but rather pass them on to the C compiler. The export clause indicates which function and variable definitions should be exported to the Cyclone code. If we only wanted to export the peek function, then we would leave the poke function out of the export list. All all other definitions, like typedefs, structs, etc., not to mention #defines and other preprocessor effects, are exported by default (but this may change in a later release).

Any top-level types you mention in the extern “C include” are interpreted by the Cyclone code that uses them as Cyclone types. If they are actually C types (as would be the case if you #included some header in the C block), this will be safe, but possibly undesirable, since they may not communicate the right information to the Cyclone code. There are two ways around this. In many cases, you can actually declare Cyclone types within the C code, and they will be treated as such. For example, in lib/core.cyc, we have For example, you could do something like:

extern "C include" {
  ... Cyc_Core_mkthin(`a ?`r dyn, sizeof_t<`a> sz) {
    unsigned bd = _get_dyneither_size(dyn,sz);
    return Cyc_Core_mktuple(dyn.curr,bd);
  } 
} export {
  Cyc_Core_mkthin
}

In this case, we are able to include a ? notation directly in the C type, but then manipulate it using the runtime system functions for fat pointers (see cyc_include.h for details).

In the case that you are including a C header file, you may not be able to change its definitions to have a proper Cyclone type, or it may be that the Cyclone definitions will not parse for some reason. In this case, you can declare a block to override the definitions with Cyclone compatible versions. For example, we could change the above code to be instead:

extern "C include" {
  struct foo { int x; int y; };
  struct foo *cast_charbuf(char *buf, unsigned int n) {
    if (n >= sizeof(struct foo))
      return (struct foo *)buf;
    else
      return (void *)0;
  }
} cyclone_override {
  struct foo *cast_charbuf
    (char * @numelts(valueof(`n)) @nozeroterm buf,tag_t<`n> n);
} export {
  cast_charbuf
}

Now we have given cast_charbuf its original C type, but then provided the Cyclone type in the override block. The Cyclone type ensures the value of n correctly represents the length of the buffer, by using Cyclone’s dependent types. Note that top-level struct and other type definitions can basically be entirely Cyclone syntax. If you try to declare a Cyclone overriding type that is representation-incompatible with the C version, the compiler will complain.

Here is a another example using an external header:

extern "C include" {  /* tell Cyclone that <pcre.h> is C code */
#include <pcre/pcre.h>
} cyclone_override {
  pcre *`U pcre_compile(const char @pattern, int options,
                        const char *`H *errptr, int *erroffset,
                        const unsigned char *tableptr);
  int pcre_exec(const pcre @code, const pcre_extra *extra, 
                const char *subject, int length,
                int startoffset, int options,
                int *ovector, int ovecsize);
} export { pcre_compile, pcre_exec; }

In this case, we have included the Perl regular expression library C header, and then exported two of its functions, pcrecompile and pcreexec. Moreover, we have given these functions Cyclone types that are more expressive in the original C. Probably we would yet want to write wrappers around these functions to check other invariants of the arguments (e.g., that the length passed to pcre_exec is indeed the length of the subject). Take a look at tests/pcredemo.cyc for more information on this example. Another example that shows how you can override things is in tests/cinclude.cyc.

The goal of this example is to show how you can safely suck in a large C interface (in this case, the Perl Compatible Regular Expression interface), write wrappers around some of the functions to convert represenations and check properties, and then safely export these wrappers to Cyclone.

One word of warning: when you #include something within an extern “C include”, it will follow the normal include path, which is to say that it will look for Cyclone versions of the headers first. This means that if you do something like:

extern "C include" {
#include <string.h>
} export { ... }

It will actually include the Cyclone version of the string.h header! These easiest way around this is to use an absolute path, as in

extern "C include" {
#include "/usr/include/string.h"
} export { ... }

Even worse is when a C header you wish to include itself includes a header for which there exists a Cyclone version. In the pcre.h example above, this actually occurs in that pcre.h includes stdlib.h, and gets the Cyclone version. To avoid this, the pcredemo.cyc program includes the Cyclone versions of these headers first. Ultimately we will probably change the compiler so that header processing within extern “C include” searches the C header path but not the Cyclone one.